Completed
Pull Request — master (#99)
by
unknown
01:14
created

APIClient.mnemonicToPrivateKey   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
c 2
b 1
f 0
nc 1
dl 0
loc 14
rs 9.4285
nop 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 5 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
var useWebWorker = require('./use-webworker')();
22
23
24
/**
25
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
26
 *
27
 * @param promise   {q.Promise}
28
 * @param cb        function
29
 * @return q.Promise
30
 */
31
function callbackify(promise, cb) {
32
    // add a .then to trigger the cb for people using callbacks
33
    if (cb) {
34
        promise
35
            .then(function(res) {
36
                // use q.nextTick for asyncness
37
                q.nextTick(function() {
38
                    cb(null, res);
39
                });
40
            }, function(err) {
41
                // use q.nextTick for asyncness
42
                q.nextTick(function() {
43
                    cb(err, null);
44
                });
45
            });
46
    }
47
48
    // return the promise for people using promises
49
    return promise;
50
}
51
52
/**
53
 * Bindings to consume the BlockTrail API
54
 *
55
 * @param options       object{
56
 *                          apiKey: 'API_KEY',
57
 *                          apiSecret: 'API_SECRET',
58
 *                          host: 'defaults to api.blocktrail.com',
59
 *                          network: 'BTC|LTC',
60
 *                          testnet: true|false
61
 *                      }
62
 * @constructor
63
 */
64
var APIClient = function(options) {
65
    var self = this;
66
67
    // handle constructor call without 'new'
68
    if (!(this instanceof APIClient)) {
69
        return new APIClient(options);
70
    }
71
    self.bitcoinCash = options.network && options.network === "BCC";
72
    self.testnet = options.testnet = options.testnet || false;
73
    if (self.bitcoinCash) {
74
        if (self.testnet) {
75
            self.network = bitcoin.networks.bitcoincashtestnet;
76
        } else {
77
            self.network = bitcoin.networks.bitcoincash;
78
        }
79
    } else {
80
        if (self.testnet) {
81
            self.network = bitcoin.networks.testnet;
82
        } else {
83
            self.network = bitcoin.networks.bitcoin;
84
        }
85
    }
86
87
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
88
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
89
90
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
91
92
    /**
93
     * @type RestClient
94
     */
95
    self.client = APIClient.initRestClient(options);
96
97
    if (options.btccom) {
98
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
99
    } else {
100
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
101
    }
102
103
};
104
105
APIClient.initRestClient = function(options) {
106
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
107
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
108
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
109
    }
110
111
    // trim off leading https?://
112
    if (options.host && options.host.indexOf("https://") === 0) {
113
        options.https = true;
114
        options.host = options.host.substr(8);
115
    } else if (options.host && options.host.indexOf("http://") === 0) {
116
        options.https = false;
117
        options.host = options.host.substr(7);
118
    }
119
120
    if (typeof options.https === "undefined") {
121
        options.https = true;
122
    }
123
124
    if (!options.port) {
125
        options.port = options.https ? 443 : 80;
126
    }
127
128
    if (options.btccom) {
129
        if (!options.host) {
130
            options.host = 'chain.api.btc.com';
131
        }
132
133
        if (!options.endpoint) {
134
            options.endpoint = "/" + (options.apiVersion || "v3");
135
        }
136
137
    } else {
138
        if (!options.host) {
139
            options.host = 'api.blocktrail.com';
140
        }
141
142
        if (!options.endpoint) {
143
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
144
        }
145
    }
146
147
    return new RestClient(options);
148
};
149
150
var determineDataStorageV2_3 = function(options) {
151
    return q.when(options)
152
        .then(function(options) {
153
            // legacy
154
            if (options.storePrimaryMnemonic) {
155
                options.storeDataOnServer = options.storePrimaryMnemonic;
156
            }
157
158
            // storeDataOnServer=false when primarySeed is provided
159
            if (typeof options.storeDataOnServer === "undefined") {
160
                options.storeDataOnServer = !options.primarySeed;
161
            }
162
163
            return options;
164
        });
165
};
166
167
var produceEncryptedDataV2 = function(options, notify) {
168
    return q.when(options)
169
        .then(function(options) {
170
            if (options.storeDataOnServer) {
171
                if (!options.secret) {
172
                    if (!options.passphrase) {
173
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
174
                    }
175
176
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
177
178
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
179
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
180
                }
181
182
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
183
184
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
185
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
186
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
187
188
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
189
190
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
191
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
192
            }
193
194
            return options;
195
        });
196
};
197
198
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
199
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
200
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
201
        var saltBuf = Encryption.generateSalt();
202
        var iv = Encryption.generateIV();
203
204
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
205
            return require('./webworker');
206
        }, onLoadWorkerLoadAsmCrypto, {
207
            method: 'Encryption.encryptWithSaltAndIV',
208
            pt: pt,
209
            pw: pw,
210
            saltBuf: saltBuf,
211
            iv: iv,
212
            iterations: iter
213
        })
214
            .then(function(data) {
215
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
216
            });
217
    } else {
218
        try {
219
            return q.when(Encryption.encrypt(pt, pw, iter));
220
        } catch (e) {
221
            return q.reject(e);
222
        }
223
    }
224
};
225
226
APIClient.prototype.promisedDecrypt = function(ct, pw) {
227
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
228
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
229
            return require('./webworker');
230
        }, onLoadWorkerLoadAsmCrypto, {
231
            method: 'Encryption.decrypt',
232
            ct: ct,
233
            pw: pw
234
        })
235
            .then(function(data) {
236
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
237
            });
238
    } else {
239
        try {
240
            return q.when(Encryption.decrypt(ct, pw));
241
        } catch (e) {
242
            return q.reject(e);
243
        }
244
    }
245
};
246
247
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
248
    var self = this;
249
250
    return q.when(options)
251
        .then(function(options) {
252
            if (options.storeDataOnServer) {
253
                return q.when()
254
                    .then(function() {
255
                        if (!options.secret) {
256
                            if (!options.passphrase) {
257
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
258
                            }
259
260
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
261
262
                            // -> now a buffer
263
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
264
265
                            // -> now a buffer
266
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
267
                                .then(function(encryptedSecret) {
268
                                    options.encryptedSecret = encryptedSecret;
269
                                });
270
                        } else {
271
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
272
                                throw new Error('Secret must be a buffer');
273
                            }
274
                        }
275
                    })
276
                    .then(function() {
277
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
278
279
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
280
                            .then(function(encryptedPrimarySeed) {
281
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
282
                            });
283
                    })
284
                    .then(function() {
285
                        // skip generating recovery secret when explicitly set to false
286
                        if (options.recoverySecret === false) {
287
                            return;
288
                        }
289
290
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
291
                        if (!options.recoverySecret) {
292
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
293
                        }
294
295
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
296
                            .then(function(recoveryEncryptedSecret) {
297
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
298
                            });
299
                    })
300
                    .then(function() {
301
                        return options;
302
                    });
303
            } else {
304
                return options;
305
            }
306
        });
307
};
308
309
var doRemainingWalletDataV2_3 = function(options, network, notify) {
310
    return q.when(options)
311
        .then(function(options) {
312
            if (!options.backupPublicKey) {
313
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
314
            }
315
316
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
317
318
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
319
320
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
321
322
            if (!options.backupPublicKey) {
323
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
324
                options.backupPublicKey = options.backupPrivateKey.neutered();
325
            }
326
327
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
328
329
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
330
331
            return options;
332
        });
333
};
334
335
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
336
    var self = this;
337
338
    var deferred = q.defer();
339
    deferred.promise.spreadNodeify(cb);
340
341
    deferred.resolve(q.fcall(function() {
342
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
343
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
344
        });
345
    }));
346
347
    return deferred.promise;
348
};
349
350
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
351
    var self = this;
352
353
    if (useWebWorker) {
354
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
355
            return require('./webworker');
356
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
357
            .then(function(data) {
358
                return data.seed;
359
            });
360
    } else {
361
        try {
362
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
363
        } catch (e) {
364
            return q.reject(e);
365
        }
366
    }
367
};
368
369
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
370
    var self = this;
371
372
    var deferred = q.defer();
373
    deferred.promise.nodeify(cb);
374
375
    try {
376
        // avoid conflicting options
377
        if (options.passphrase && options.password) {
378
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
379
        }
380
        // normalize passphrase/password
381
        options.passphrase = options.passphrase || options.password;
382
        delete options.password;
383
384
        // avoid conflicting options
385
        if (options.primaryMnemonic && options.primarySeed) {
386
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
387
        }
388
389
        // avoid deprecated options
390
        if (options.primaryPrivateKey) {
391
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
392
        }
393
394
        // make sure we have at least one thing to use
395
        if (!options.primaryMnemonic && !options.primarySeed) {
396
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
397
        }
398
399
        if (options.primarySeed) {
400
            self.primarySeed = options.primarySeed;
401
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
402
            deferred.resolve(options);
403
        } else {
404
            if (!options.passphrase) {
405
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
406
            }
407
408
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
409
                .then(function(seedHex) {
410
                    try {
411
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
412
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
413
                        deferred.resolve(options);
414
                    } catch (e) {
415
                        deferred.reject(e);
416
                    }
417
                }, function(e) {
418
                    deferred.reject(e);
419
                });
420
        }
421
    } catch (e) {
422
        deferred.reject(e);
423
    }
424
425
    return deferred.promise;
426
};
427
428
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
429
    var self = this;
430
431
    var deferred = q.defer();
432
    deferred.promise.nodeify(cb);
433
434
    try {
435
        // avoid conflicting options
436
        if (options.backupMnemonic && options.backupPublicKey) {
437
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
438
        }
439
440
        // make sure we have at least one thing to use
441
        if (!options.backupMnemonic && !options.backupPublicKey) {
442
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
443
        }
444
445
        if (options.backupPublicKey) {
446
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
447
                deferred.resolve(options);
448
            } else {
449
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
450
                deferred.resolve(options);
451
            }
452
        } else {
453
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
454
                options.backupPublicKey = backupPrivateKey.neutered();
455
                deferred.resolve(options);
456
            }, function(e) {
457
                deferred.reject(e);
458
            });
459
        }
460
    } catch (e) {
461
        deferred.reject(e);
462
    }
463
464
    return deferred.promise;
465
};
466
467
APIClient.prototype.debugAuth = function(cb) {
468
    var self = this;
469
470
    return self.client.get("/debug/http-signature", null, true, cb);
471
};
472
473
/**
474
 * get a single address
475
 *
476
 * @param address      string       address hash
477
 * @param [cb]          function    callback function to call when request is complete
478
 * @return q.Promise
479
 */
480
APIClient.prototype.address = function(address, cb) {
481
    var self = this;
482
// Hit by the rate limit for btc.com - Fix in progress
483
    return callbackify(self.client.get(self.converter.getUrlForAddress(address), null)
484
        .then(function(data) {
485
            return self.converter.handleErros(self, data)})
486
        .then(function(data) {
487
            if (data === null) {
488
                return data;
489
            } else {
490
                if (self.converter instanceof BtccomConverter) {
491
                    return self.client.get(self.converter.getUrlForTransaction(data.data.first_tx), null)
492
                        .then(function(result) {
493
                            return [data, result.data.created_at];
494
                        })
495
                        .then(function(dataAndFirstSeenTime) {
496
                            if (dataAndFirstSeenTime !== null) {
497
                                var data = dataAndFirstSeenTime[0];
498
                                var firstSeenTime = dataAndFirstSeenTime[1];
499
                                return self.converter.convertAddress(data, firstSeenTime);
500
                            } else {
501
                                return dataAndFirstSeenTime;
502
                            }
503
                        })
504
                } else {
505
                    return self.converter.convertAddress(data);
506
                }
507
            }
508
        }), cb);
509
};
510
511
APIClient.prototype.addresses = function(addresses, cb) {
512
    var self = this;
513
514
    return callbackify(self.client.post("/address", null, {"addresses": addresses}), cb);
515
};
516
517
518
/**
519
 * get all transactions for an address (paginated)
520
 *
521
 * @param address       string      address hash
522
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
523
 * @param [cb]          function    callback function to call when request is complete
524
 * @return q.Promise
525
 */
526
APIClient.prototype.addressTransactions = function(address, params, cb) {
527
528
    var self = this;
529
530
    if (typeof params === "function") {
531
        cb = params;
532
        params = null;
533
    }
534
535
    return callbackify(self.client.get(self.converter.getUrlForAddressTransactions(address), params)
536
        .then(function(data) {
537
            return self.converter.handleErros(self, data)})
538
        .then(function(data) {
539
            return data.data === null ? data : self.converter.convertAddressTx(data);
540
        }), cb);
541
};
542
543
/**
544
 * get all transactions for a batch of addresses (paginated)
545
 *
546
 * @param addresses     array       address hashes
547
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
548
 * @param [cb]          function    callback function to call when request is complete
549
 * @return q.Promise
550
 */
551
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
552
    var self = this;
553
554
    if (typeof params === "function") {
555
        cb = params;
556
        params = null;
557
    }
558
559
    return self.client.post("/address/has-transactions", params, {"addresses": addresses}, cb);
560
};
561
562
/**
563
 * get all unconfirmed transactions for an address (paginated)
564
 *
565
 * @param address       string      address hash
566
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
567
 * @param [cb]          function    callback function to call when request is complete
568
 * @return q.Promise
569
 */
570
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
571
    var self = this;
572
573
    if (typeof params === "function") {
574
        cb = params;
575
        params = null;
576
    }
577
578
    return self.client.get("/address/" + address + "/unconfirmed-transactions", params, cb);
579
};
580
581
/**
582
 * get all unspent outputs for an address (paginated)
583
 *
584
 * @param address       string      address hash
585
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
586
 * @param [cb]          function    callback function to call when request is complete
587
 * @return q.Promise
588
 */
589
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
590
    var self = this;
591
592
    if (typeof params === "function") {
593
        cb = params;
594
        params = null;
595
    }
596
597
    return callbackify(self.client.get(self.converter.getUrlForAddressUnspent(address), params)
598
        .then(function(data) {
599
            return self.converter.handleErros(self, data)})
600
        .then(function(data) {
601
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address)
602
        }), cb);
603
};
604
605
/**
606
 * get all unspent outputs for a batch of addresses (paginated)
607
 *
608
 * @param addresses     array       address hashes
609
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
610
 * @param [cb]          function    callback function to call when request is complete
611
 * @return q.Promise
612
 */
613
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
614
    var self = this;
615
616
    if (typeof params === "function") {
617
        cb = params;
618
        params = null;
619
    }
620
621
    return self.client.post("/address/unspent-outputs", params, {"addresses": addresses}, cb);
622
};
623
624
/**
625
 * verify ownership of an address
626
 *
627
 * @param address       string      address hash
628
 * @param signature     string      a signed message (the address hash) using the private key of the address
629
 * @param [cb]          function    callback function to call when request is complete
630
 * @return q.Promise
631
 */
632
APIClient.prototype.verifyAddress = function(address, signature, cb) {
633
    var self = this;
634
635
    return self.client.post("/address/" + address + "/verify", null, {signature: signature}, cb);
636
};
637
638
/**
639
 *
640
 * get all blocks (paginated)
641
 * ASK
642
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
643
 * @param [cb]          function    callback function to call when request is complete
644
 * @return q.Promise
645
 */
646
APIClient.prototype.allBlocks = function(params, cb) {
647
    var self = this;
648
649
    if (typeof params === "function") {
650
        cb = params;
651
        params = null;
652
    }
653
654
    /*
655
     iN PROGRESS
656
657
      return callbackify(self.client.get(self.converter.getUrlForAllblocks(), null)
658
        .then(function(data) {
659
            if(self.converter instanceof BtccomConverter) {
660
            }
661
         }), cb);
662
    */
663
664
    return self.client.get("/all-blocks", params, cb);
665
};
666
667
/**
668
 * get a block
669
 *
670
 * @param block         string|int  a block hash or a block height
671
 * @param [cb]          function    callback function to call when request is complete
672
 * @return q.Promise
673
 */
674
APIClient.prototype.block = function(block, cb) {
675
    var self = this;
676
677
    return callbackify(self.client.get(self.converter.getUrlForBlock(block), null)
678
        .then(function(data) {
679
            return self.converter.handleErros(self, data)})
680
        .then(function(data) {
681
            return data.data === null ? data : self.converter.convertBlock(data)
682
        }), cb);
683
};
684
685
/**
686
 * TODO btc.com
687
 * get the latest block
688
 *
689
 * @param [cb]          function    callback function to call when request is complete
690
 * @return q.Promise
691
 */
692
APIClient.prototype.blockLatest = function(cb) {
693
    var self = this;
694
695
    return callbackify(self.client.get("/block/latest", null), cb);
696
};
697
698
/**
699
 * get all transactions for a block (paginated)
700
 *
701
 * @param block         string|int  a block hash or a block height
702
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
703
 * @param [cb]          function    callback function to call when request is complete
704
 * @return q.Promise
705
 */
706
APIClient.prototype.blockTransactions = function(block, params, cb) {
707
    var self = this;
708
709
    if (typeof params === "function") {
710
        cb = params;
711
        params = null;
712
    }
713
714
    return callbackify(self.client.get(self.converter.getUrlForBlockTransaction(block), params)
715
        .then(function(data) {
716
            return self.converter.handleErros(self, data)})
717
        .then(function(data) {
718
            return data.data ===  null ? data : self.converter.convertBlockTx(data)
719
        }), cb);
720
};
721
722
/**
723
 * get a single transaction
724
 *
725
 * @param tx            string      transaction hash
726
 * @param [cb]          function    callback function to call when request is complete
727
 * @return q.Promise
728
 */
729
APIClient.prototype.transaction = function(tx, cb) {
730
    var self = this;
731
732
    return callbackify(self.client.get(self.converter.getUrlForTransaction(tx), null)
733
        .then(function(data) {
734
            return self.converter.handleErros(self, data)})
735
        .then(function(data) {
736
            if (data.data === null) {
737
                return data;
738
            } else {
739
                if (self.converter instanceof BtccomConverter) {
740
                    var txPath = data.data.hash + ".rawhex";
741
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
742
                        .then(function(rawTx) {
743
                            return [data, rawTx]
744
                        })
745
                        .then(function(dataAndTx) {
746
                            if (dataAndTx !== null) {
747
                                var data = dataAndTx[0];
748
                                var rawTx = dataAndTx[1];
749
                                return self.converter.convertTx(data, rawTx);
750
                            } else {
751
                                return dataAndTx;
752
                            }
753
                        })
754
                } else {
755
                    return self.converter.convertTx(data)
756
                }
757
            }
758
        }), cb);
759
};
760
761
/**
762
 * get a batch of transactions
763
 *
764
 * @param txs           string[]    list of transaction hashes (txId)
765
 * @param [cb]          function    callback function to call when request is complete
766
 * @return q.Promise
767
 */
768
APIClient.prototype.transactions = function(txs, cb) {
769
    var self = this;
770
771
    return callbackify(self.client.post("/transactions", null, txs), cb, false);
0 ignored issues
show
Bug introduced by
The call to callbackify seems to have too many arguments starting with false.
Loading history...
772
};
773
774
/**
775
 * get a paginated list of all webhooks associated with the api user
776
 *
777
 * @param [params]      object      pagination: {page: 1, limit: 20}
778
 * @param [cb]          function    callback function to call when request is complete
779
 * @return q.Promise
780
 */
781
APIClient.prototype.allWebhooks = function(params, cb) {
782
    var self = this;
783
784
    if (typeof params === "function") {
785
        cb = params;
786
        params = null;
787
    }
788
789
    return self.client.get("/webhooks", params, cb);
790
};
791
792
/**
793
 * create a new webhook
794
 *
795
 * @param url           string      the url to receive the webhook events
796
 * @param [identifier]  string      a unique identifier associated with the webhook
797
 * @param [cb]          function    callback function to call when request is complete
798
 * @return q.Promise
799
 */
800
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
801
    var self = this;
802
803
    if (typeof identifier === "function") {
804
        //mimic function overloading
805
        cb = identifier;
806
        identifier = null;
807
    }
808
809
    return self.client.post("/webhook", null, {url: url, identifier: identifier}, cb);
810
};
811
812
/**
813
 * Converts a cash address to the legacy (base58) format
814
 * @param {string} input
815
 * @returns {string}
816
 */
817
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
818
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
819
        var address;
820
        try {
821
            bitcoin.address.fromBase58Check(input, this.network);
822
            return input;
823
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
824
825
        address = bitcoin.address.fromCashAddress(input, this.network);
826
        var prefix;
827
        if (address.version === bitcoin.script.types.P2PKH) {
828
            prefix = this.network.pubKeyHash;
829
        } else if (address.version === bitcoin.script.types.P2SH) {
830
            prefix = this.network.scriptHash;
831
        } else {
832
            throw new Error("Unsupported address type");
833
        }
834
835
        return bitcoin.address.toBase58Check(address.hash, prefix);
836
    }
837
838
    throw new Error("Cash addresses only work on bitcoin cash");
839
};
840
841
/**
842
 * Converts a legacy bitcoin to the new cashaddr format
843
 * @param {string} input
844
 * @returns {string}
845
 */
846
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
847
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
848
        var address;
849
        try {
850
            bitcoin.address.fromCashAddress(input, this.network);
851
            return input;
852
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
853
854
        address = bitcoin.address.fromBase58Check(input, this.network);
855
        var scriptType;
856
        if (address.version === this.network.pubKeyHash) {
857
            scriptType = bitcoin.script.types.P2PKH;
858
        } else if (address.version === this.network.scriptHash) {
859
            scriptType = bitcoin.script.types.P2SH;
860
        } else {
861
            throw new Error("Unsupported address type");
862
        }
863
864
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
865
    }
866
867
    throw new Error("Cash addresses only work on bitcoin cash");
868
};
869
870
/**
871
 * get an existing webhook by it's identifier
872
 *
873
 * @param identifier    string      the unique identifier of the webhook to get
874
 * @param [cb]          function    callback function to call when request is complete
875
 * @return q.Promise
876
 */
877
APIClient.prototype.getWebhook = function(identifier, cb) {
878
    var self = this;
879
880
    return self.client.get("/webhook/" + identifier, null, cb);
881
};
882
883
/**
884
 * update an existing webhook
885
 *
886
 * @param identifier    string      the unique identifier of the webhook
887
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
888
 * @param [cb]          function    callback function to call when request is complete
889
 * @return q.Promise
890
 */
891
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
892
    var self = this;
893
894
    return self.client.put("/webhook/" + identifier, null, webhookData, cb);
895
};
896
897
/**
898
 * deletes an existing webhook and any event subscriptions associated with it
899
 *
900
 * @param identifier    string      the unique identifier of the webhook
901
 * @param [cb]          function    callback function to call when request is complete
902
 * @return q.Promise
903
 */
904
APIClient.prototype.deleteWebhook = function(identifier, cb) {
905
    var self = this;
906
907
    return self.client.delete("/webhook/" + identifier, null, null, cb);
908
};
909
910
/**
911
 * get a paginated list of all the events a webhook is subscribed to
912
 *
913
 * @param identifier    string      the unique identifier of the webhook
914
 * @param [params]      object      pagination: {page: 1, limit: 20}
915
 * @param [cb]          function    callback function to call when request is complete
916
 * @return q.Promise
917
 */
918
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
919
    var self = this;
920
921
    if (typeof params === "function") {
922
        cb = params;
923
        params = null;
924
    }
925
926
    return self.client.get("/webhook/" + identifier + "/events", params, cb);
927
};
928
929
/**
930
 * subscribes a webhook to transaction events for a particular transaction
931
 *
932
 * @param identifier    string      the unique identifier of the webhook
933
 * @param transaction   string      the transaction hash
934
 * @param confirmations integer     the amount of confirmations to send
935
 * @param [cb]          function    callback function to call when request is complete
936
 * @return q.Promise
937
 */
938
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
939
    var self = this;
940
    var postData = {
941
        'event_type': 'transaction',
942
        'transaction': transaction,
943
        'confirmations': confirmations
944
    };
945
946
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
947
};
948
949
/**
950
 * subscribes a webhook to transaction events on a particular address
951
 *
952
 * @param identifier    string      the unique identifier of the webhook
953
 * @param address       string      the address hash
954
 * @param confirmations integer     the amount of confirmations to send
955
 * @param [cb]          function    callback function to call when request is complete
956
 * @return q.Promise
957
 */
958
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
959
    var self = this;
960
    var postData = {
961
        'event_type': 'address-transactions',
962
        'address': address,
963
        'confirmations': confirmations
964
    };
965
966
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
967
};
968
969
/**
970
 * batch subscribes a webhook to multiple transaction events
971
 *
972
 * @param  identifier   string      the unique identifier of the webhook
973
 * @param  batchData    array       An array of objects containing batch event data:
974
 *                                  {address : 'address', confirmations : 'confirmations']
975
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
976
 * @param [cb]          function    callback function to call when request is complete
977
 * @return q.Promise
978
 */
979
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
980
    var self = this;
981
    batchData.forEach(function(record) {
982
        record.event_type = 'address-transactions';
983
    });
984
985
    return self.client.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
986
};
987
988
/**
989
 * subscribes a webhook to a new block event
990
 *
991
 * @param identifier    string      the unique identifier of the webhook
992
 * @param [cb]          function    callback function to call when request is complete
993
 * @return q.Promise
994
 */
995
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
996
    var self = this;
997
    var postData = {
998
        'event_type': 'block'
999
    };
1000
1001
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
1002
};
1003
1004
/**
1005
 * removes an address transaction event subscription from a webhook
1006
 *
1007
 * @param identifier    string      the unique identifier of the webhook
1008
 * @param address       string      the address hash
1009
 * @param [cb]          function    callback function to call when request is complete
1010
 * @return q.Promise
1011
 */
1012
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1013
    var self = this;
1014
1015
    return self.client.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1016
};
1017
1018
/**
1019
 * removes an transaction event subscription from a webhook
1020
 *
1021
 * @param identifier    string      the unique identifier of the webhook
1022
 * @param transaction   string      the transaction hash
1023
 * @param [cb]          function    callback function to call when request is complete
1024
 * @return q.Promise
1025
 */
1026
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1027
    var self = this;
1028
1029
    return self.client.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1030
};
1031
1032
/**
1033
 * removes a block event subscription from a webhook
1034
 *
1035
 * @param identifier    string      the unique identifier of the webhook
1036
 * @param [cb]          function    callback function to call when request is complete
1037
 * @return q.Promise
1038
 */
1039
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1040
    var self = this;
1041
1042
    return self.client.delete("/webhook/" + identifier + "/block", null, null, cb);
1043
};
1044
1045
/**
1046
 * initialize an existing wallet
1047
 *
1048
 * Either takes two argument:
1049
 * @param options       object      {}
1050
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1051
 *
1052
 * Or takes three arguments (old, deprecated syntax):
1053
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1054
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1055
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1050. The second definition is ignored.
Loading history...
1056
 *
1057
 * @returns {q.Promise}
1058
 */
1059
APIClient.prototype.initWallet = function(options, cb) {
1060
    var self = this;
1061
1062
    if (typeof options !== "object") {
1063
        // get the old-style arguments
1064
        options = {
1065
            identifier: arguments[0],
1066
            passphrase: arguments[1]
1067
        };
1068
1069
        cb = arguments[2];
1070
    }
1071
1072
    if (options.check_backup_key) {
1073
        if (typeof options.check_backup_key !== "string") {
1074
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1075
        }
1076
    }
1077
1078
    var deferred = q.defer();
1079
    deferred.promise.spreadNodeify(cb);
1080
1081
    var identifier = options.identifier;
1082
1083
    if (!identifier) {
1084
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1085
        return deferred.promise;
1086
    }
1087
1088
    deferred.resolve(self.client.get("/wallet/" + identifier, null, true).then(function(result) {
1089
        var keyIndex = options.keyIndex || result.key_index;
1090
1091
        options.walletVersion = result.wallet_version;
1092
1093
        if (options.check_backup_key) {
1094
            if (options.check_backup_key !== result.backup_public_key[0]) {
1095
                throw new Error("Backup key returned from server didn't match our own copy");
1096
            }
1097
        }
1098
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1099
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1100
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1101
        });
1102
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1103
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1104
        });
1105
1106
        // initialize wallet
1107
        var wallet = new Wallet(
1108
            self,
1109
            identifier,
1110
            options.walletVersion,
1111
            result.primary_mnemonic,
1112
            result.encrypted_primary_seed,
1113
            result.encrypted_secret,
1114
            primaryPublicKeys,
1115
            backupPublicKey,
1116
            blocktrailPublicKeys,
1117
            keyIndex,
1118
            result.segwit || 0,
1119
            self.testnet,
1120
            result.checksum,
1121
            result.upgrade_key_index,
1122
            options.useCashAddress,
1123
            options.bypassNewAddressCheck
1124
        );
1125
1126
        wallet.recoverySecret = result.recovery_secret;
1127
1128
        if (!options.readOnly) {
1129
            return wallet.unlock(options).then(function() {
1130
                return wallet;
1131
            });
1132
        } else {
1133
            return wallet;
1134
        }
1135
    }));
1136
1137
    return deferred.promise;
1138
};
1139
1140
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1141
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1142
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1143
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1144
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1145
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1146
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1147
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1148
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1149
1150
/**
1151
 * create a new wallet
1152
 *   - will generate a new primary seed and backup seed
1153
 *
1154
 * Either takes two argument:
1155
 * @param options       object      {}
1156
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) // nocommit @TODO
1157
 *
1158
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1159
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1155. The second definition is ignored.
Loading history...
1160
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1156. The second definition is ignored.
Loading history...
1161
 *
1162
 * Or takes four arguments (old, deprecated syntax):
1163
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1164
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1165
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1166
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1156. The second definition is ignored.
Loading history...
1167
 * @returns {q.Promise}
1168
 */
1169
APIClient.prototype.createNewWallet = function(options, cb) {
1170
    /* jshint -W071, -W074 */
1171
1172
    var self = this;
1173
1174
    if (typeof options !== "object") {
1175
        // get the old-style arguments
1176
        var identifier = arguments[0];
1177
        var passphrase = arguments[1];
1178
        var keyIndex = arguments[2];
1179
        cb = arguments[3];
1180
1181
        // keyIndex is optional
1182
        if (typeof keyIndex === "function") {
1183
            cb = keyIndex;
1184
            keyIndex = null;
1185
        }
1186
1187
        options = {
1188
            identifier: identifier,
1189
            passphrase: passphrase,
1190
            keyIndex: keyIndex
1191
        };
1192
    }
1193
1194
    // default to v3
1195
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1196
1197
    var deferred = q.defer();
1198
    deferred.promise.spreadNodeify(cb);
1199
1200
    q.nextTick(function() {
1201
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1202
1203
        options.keyIndex = options.keyIndex || 0;
1204
        options.passphrase = options.passphrase || options.password;
1205
        delete options.password;
1206
1207
        if (!options.identifier) {
1208
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1209
            return deferred.promise;
1210
        }
1211
1212
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1213
            self._createNewWalletV1(options)
1214
                .progress(function(p) { deferred.notify(p); })
1215
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1216
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1217
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1218
            self._createNewWalletV2(options)
1219
                .progress(function(p) { deferred.notify(p); })
1220
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1221
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1222
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1223
            self._createNewWalletV3(options)
1224
                .progress(function(p) { deferred.notify(p); })
1225
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1226
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1227
        } else {
1228
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1229
        }
1230
    });
1231
1232
    return deferred.promise;
1233
};
1234
1235
APIClient.prototype._createNewWalletV1 = function(options) {
1236
    var self = this;
1237
1238
    var deferred = q.defer();
1239
1240
    q.nextTick(function() {
1241
1242
        if (!options.primaryMnemonic && !options.primarySeed) {
1243
            if (!options.passphrase && !options.password) {
1244
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1245
                return deferred.promise;
1246
            } else {
1247
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1248
                if (options.storePrimaryMnemonic !== false) {
1249
                    options.storePrimaryMnemonic = true;
1250
                }
1251
            }
1252
        }
1253
1254
        if (!options.backupMnemonic && !options.backupPublicKey) {
1255
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1256
        }
1257
1258
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1259
1260
        self.resolvePrimaryPrivateKeyFromOptions(options)
1261
            .then(function(options) {
1262
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1263
1264
                return self.resolveBackupPublicKeyFromOptions(options)
1265
                    .then(function(options) {
1266
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1267
1268
                        // create a checksum of our private key which we'll later use to verify we used the right password
1269
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1270
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1271
                        var keyIndex = options.keyIndex;
1272
1273
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1274
1275
                        // send the public keys to the server to store them
1276
                        //  and the mnemonic, which is safe because it's useless without the password
1277
                        return self.storeNewWalletV1(
1278
                            options.identifier,
1279
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1280
                            [options.backupPublicKey.toBase58(), "M"],
1281
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1282
                            checksum,
1283
                            keyIndex,
1284
                            options.segwit || null
1285
                        )
1286
                            .then(function(result) {
1287
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1288
1289
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1290
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1291
                                });
1292
1293
                                var wallet = new Wallet(
1294
                                    self,
1295
                                    options.identifier,
1296
                                    Wallet.WALLET_VERSION_V1,
1297
                                    options.primaryMnemonic,
1298
                                    null,
1299
                                    null,
1300
                                    {keyIndex: primaryPublicKey},
1301
                                    options.backupPublicKey,
1302
                                    blocktrailPublicKeys,
1303
                                    keyIndex,
1304
                                    result.segwit || 0,
1305
                                    self.testnet,
1306
                                    checksum,
1307
                                    result.upgrade_key_index,
1308
                                    options.useCashAddress,
1309
                                    options.bypassNewAddressCheck
1310
                                );
1311
1312
                                return wallet.unlock({
1313
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1314
                                    passphrase: options.passphrase,
1315
                                    primarySeed: options.primarySeed,
1316
                                    primaryMnemonic: null // explicit null
1317
                                }).then(function() {
1318
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1319
                                    return [
1320
                                        wallet,
1321
                                        {
1322
                                            walletVersion: wallet.walletVersion,
1323
                                            primaryMnemonic: options.primaryMnemonic,
1324
                                            backupMnemonic: options.backupMnemonic,
1325
                                            blocktrailPublicKeys: blocktrailPublicKeys
1326
                                        }
1327
                                    ];
1328
                                });
1329
                            });
1330
                    }
1331
                );
1332
            })
1333
            .then(
1334
            function(r) {
1335
                deferred.resolve(r);
1336
            },
1337
            function(e) {
1338
                deferred.reject(e);
1339
            }
1340
        )
1341
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1342
    });
1343
1344
    return deferred.promise;
1345
};
1346
1347 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1348
    var self = this;
1349
1350
    var deferred = q.defer();
1351
1352
    // avoid modifying passed options
1353
    options = _.merge({}, options);
1354
1355
    determineDataStorageV2_3(options)
1356
        .then(function(options) {
1357
            options.passphrase = options.passphrase || options.password;
1358
            delete options.password;
1359
1360
            // avoid deprecated options
1361
            if (options.primaryPrivateKey) {
1362
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1363
            }
1364
1365
            // seed should be provided or generated
1366
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1367
1368
            return options;
1369
        })
1370
        .then(function(options) {
1371
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1372
        })
1373
        .then(function(options) {
1374
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1375
        })
1376
        .then(function(options) {
1377
            // create a checksum of our private key which we'll later use to verify we used the right password
1378
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1379
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1380
            var keyIndex = options.keyIndex;
1381
1382
            // send the public keys and encrypted data to server
1383
            return self.storeNewWalletV2(
1384
                options.identifier,
1385
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1386
                [options.backupPublicKey.toBase58(), "M"],
1387
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1388
                options.storeDataOnServer ? options.encryptedSecret : false,
1389
                options.storeDataOnServer ? options.recoverySecret : false,
1390
                checksum,
1391
                keyIndex,
1392
                options.support_secret || null,
1393
                options.segwit || null
1394
            )
1395
                .then(
1396
                function(result) {
1397
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1398
1399
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1400
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1401
                    });
1402
1403
                    var wallet = new Wallet(
1404
                        self,
1405
                        options.identifier,
1406
                        Wallet.WALLET_VERSION_V2,
1407
                        null,
1408
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1409
                        options.storeDataOnServer ? options.encryptedSecret : null,
1410
                        {keyIndex: options.primaryPublicKey},
1411
                        options.backupPublicKey,
1412
                        blocktrailPublicKeys,
1413
                        keyIndex,
1414
                        result.segwit || 0,
1415
                        self.testnet,
1416
                        checksum,
1417
                        result.upgrade_key_index,
1418
                        options.useCashAddress,
1419
                        options.bypassNewAddressCheck
1420
                    );
1421
1422
                    // pass along decrypted data to avoid extra work
1423
                    return wallet.unlock({
1424
                        walletVersion: Wallet.WALLET_VERSION_V2,
1425
                        passphrase: options.passphrase,
1426
                        primarySeed: options.primarySeed,
1427
                        secret: options.secret
1428
                    }).then(function() {
1429
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1430
                        return [
1431
                            wallet,
1432
                            {
1433
                                walletVersion: wallet.walletVersion,
1434
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1435
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1436
                                    null,
1437
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1438
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1439
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1440
                                    null,
1441
                                encryptedSecret: options.encryptedSecret ?
1442
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1443
                                    null,
1444
                                blocktrailPublicKeys: blocktrailPublicKeys
1445
                            }
1446
                        ];
1447
                    });
1448
                }
1449
            );
1450
        })
1451
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1452
1453
    return deferred.promise;
1454
};
1455
1456 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1457
    var self = this;
1458
1459
    var deferred = q.defer();
1460
1461
    // avoid modifying passed options
1462
    options = _.merge({}, options);
1463
1464
    determineDataStorageV2_3(options)
1465
        .then(function(options) {
1466
            options.passphrase = options.passphrase || options.password;
1467
            delete options.password;
1468
1469
            // avoid deprecated options
1470
            if (options.primaryPrivateKey) {
1471
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1472
            }
1473
1474
            // seed should be provided or generated
1475
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1476
1477
            return options;
1478
        })
1479
        .then(function(options) {
1480
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1481
        })
1482
        .then(function(options) {
1483
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1484
        })
1485
        .then(function(options) {
1486
            // create a checksum of our private key which we'll later use to verify we used the right password
1487
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1488
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1489
            var keyIndex = options.keyIndex;
1490
1491
            // send the public keys and encrypted data to server
1492
            return self.storeNewWalletV3(
1493
                options.identifier,
1494
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1495
                [options.backupPublicKey.toBase58(), "M"],
1496
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1497
                options.storeDataOnServer ? options.encryptedSecret : false,
1498
                options.storeDataOnServer ? options.recoverySecret : false,
1499
                checksum,
1500
                keyIndex,
1501
                options.support_secret || null,
1502
                options.segwit || null
1503
            )
1504
                .then(
1505
                    // result, deferred, self(apiclient)
1506
                    function(result) {
1507
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1508
1509
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1510
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1511
                        });
1512
1513
                        var wallet = new Wallet(
1514
                            self,
1515
                            options.identifier,
1516
                            Wallet.WALLET_VERSION_V3,
1517
                            null,
1518
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1519
                            options.storeDataOnServer ? options.encryptedSecret : null,
1520
                            {keyIndex: options.primaryPublicKey},
1521
                            options.backupPublicKey,
1522
                            blocktrailPublicKeys,
1523
                            keyIndex,
1524
                            result.segwit || 0,
1525
                            self.testnet,
1526
                            checksum,
1527
                            result.upgrade_key_index,
1528
                            options.useCashAddress,
1529
                            options.bypassNewAddressCheck
1530
                        );
1531
1532
                        // pass along decrypted data to avoid extra work
1533
                        return wallet.unlock({
1534
                            walletVersion: Wallet.WALLET_VERSION_V3,
1535
                            passphrase: options.passphrase,
1536
                            primarySeed: options.primarySeed,
1537
                            secret: options.secret
1538
                        }).then(function() {
1539
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1540
                            return [
1541
                                wallet,
1542
                                {
1543
                                    walletVersion: wallet.walletVersion,
1544
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1545
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1546
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1547
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1548
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1549
                                    blocktrailPublicKeys: blocktrailPublicKeys
1550
                                }
1551
                            ];
1552
                        });
1553
                    }
1554
                );
1555
        })
1556
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1557
1558
    return deferred.promise;
1559
};
1560
1561
function verifyPublicBip32Key(bip32Key, network) {
1562
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1563
    if (typeof hk.keyPair.d !== "undefined") {
1564
        throw new Error('BIP32Key contained private key material - abort');
1565
    }
1566
1567
    if (bip32Key[1].slice(0, 1) !== "M") {
1568
        throw new Error("BIP32Key contained non-public path - abort");
1569
    }
1570
}
1571
1572
function verifyPublicOnly(walletData, network) {
1573
    verifyPublicBip32Key(walletData.primary_public_key, network);
1574
    verifyPublicBip32Key(walletData.backup_public_key, network);
1575
}
1576
1577
/**
1578
 * create wallet using the API
1579
 *
1580
 * @param identifier            string      the wallet identifier to create
1581
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1582
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1583
 * @param primaryMnemonic       string      mnemonic to store
1584
 * @param checksum              string      checksum to store
1585
 * @param keyIndex              int         keyIndex that was used to create wallet
1586
 * @param segwit                bool
1587
 * @returns {q.Promise}
1588
 */
1589
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1590
                                                checksum, keyIndex, segwit) {
1591
    var self = this;
1592
1593
    var postData = {
1594
        identifier: identifier,
1595
        wallet_version: Wallet.WALLET_VERSION_V1,
1596
        primary_public_key: primaryPublicKey,
1597
        backup_public_key: backupPublicKey,
1598
        primary_mnemonic: primaryMnemonic,
1599
        checksum: checksum,
1600
        key_index: keyIndex,
1601
        segwit: segwit
1602
    };
1603
1604
    verifyPublicOnly(postData, self.network);
1605
1606
    return self.client.post("/wallet", null, postData);
1607
};
1608
1609
/**
1610
 * create wallet using the API
1611
 *
1612
 * @param identifier            string      the wallet identifier to create
1613
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1614
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1615
 * @param encryptedPrimarySeed  string      openssl format
1616
 * @param encryptedSecret       string      openssl format
1617
 * @param recoverySecret        string      openssl format
1618
 * @param checksum              string      checksum to store
1619
 * @param keyIndex              int         keyIndex that was used to create wallet
1620
 * @param supportSecret         string
1621
 * @param segwit                bool
1622
 * @returns {q.Promise}
1623
 */
1624
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1625
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1626
    var self = this;
1627
1628
    var postData = {
1629
        identifier: identifier,
1630
        wallet_version: Wallet.WALLET_VERSION_V2,
1631
        primary_public_key: primaryPublicKey,
1632
        backup_public_key: backupPublicKey,
1633
        encrypted_primary_seed: encryptedPrimarySeed,
1634
        encrypted_secret: encryptedSecret,
1635
        recovery_secret: recoverySecret,
1636
        checksum: checksum,
1637
        key_index: keyIndex,
1638
        support_secret: supportSecret || null,
1639
        segwit: segwit
1640
    };
1641
1642
    verifyPublicOnly(postData, self.network);
1643
1644
    return self.client.post("/wallet", null, postData);
1645
};
1646
1647
/**
1648
 * create wallet using the API
1649
 *
1650
 * @param identifier            string      the wallet identifier to create
1651
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1652
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1653
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1654
 * @param encryptedSecret       Buffer      buffer of ciphertext
1655
 * @param recoverySecret        Buffer      buffer of recovery secret
1656
 * @param checksum              string      checksum to store
1657
 * @param keyIndex              int         keyIndex that was used to create wallet
1658
 * @param supportSecret         string
1659
 * @param segwit                bool
1660
 * @returns {q.Promise}
1661
 */
1662
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1663
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1664
    var self = this;
1665
1666
    var postData = {
1667
        identifier: identifier,
1668
        wallet_version: Wallet.WALLET_VERSION_V3,
1669
        primary_public_key: primaryPublicKey,
1670
        backup_public_key: backupPublicKey,
1671
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1672
        encrypted_secret: encryptedSecret.toString('base64'),
1673
        recovery_secret: recoverySecret.toString('hex'),
1674
        checksum: checksum,
1675
        key_index: keyIndex,
1676
        support_secret: supportSecret || null,
1677
        segwit: segwit
1678
    };
1679
1680
    verifyPublicOnly(postData, self.network);
1681
1682
    return self.client.post("/wallet", null, postData);
1683
};
1684
1685
/**
1686
 * create wallet using the API
1687
 *
1688
 * @param identifier            string      the wallet identifier to create
1689
 * @param postData              object
1690
 * @param [cb]                  function    callback(err, result)
1691
 * @returns {q.Promise}
1692
 */
1693
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1694
    var self = this;
1695
1696
    return self.client.post("/wallet/" + identifier, null, postData, cb);
1697
};
1698
1699
/**
1700
 * upgrade wallet to use a new account number
1701
 *  the account number specifies which blocktrail cosigning key is used
1702
 *
1703
 * @param identifier            string      the wallet identifier
1704
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1705
 * @param keyIndex              int         keyIndex that was used to create wallet
1706
 * @param [cb]                  function    callback(err, result)
1707
 * @returns {q.Promise}
1708
 */
1709
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1710
    var self = this;
1711
1712
    return self.client.post("/wallet/" + identifier + "/upgrade", null, {
1713
        key_index: keyIndex,
1714
        primary_public_key: primaryPublicKey
1715
    }, cb);
1716
};
1717
1718
/**
1719
 * get the balance for the wallet
1720
 *
1721
 * @param identifier            string      the wallet identifier
1722
 * @param [cb]                  function    callback(err, result)
1723
 * @returns {q.Promise}
1724
 */
1725
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1726
    var self = this;
1727
1728
    return self.client.get("/wallet/" + identifier + "/balance", null, true, cb);
1729
};
1730
1731
/**
1732
 * do HD wallet discovery for the wallet
1733
 *
1734
 * @param identifier            string      the wallet identifier
1735
 * @param [cb]                  function    callback(err, result)
1736
 * @returns {q.Promise}
1737
 */
1738
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1739
    var self = this;
1740
1741
    return self.client.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1742
};
1743
1744
1745
/**
1746
 * get a new derivation number for specified parent path
1747
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1748
 *
1749
 * @param identifier            string      the wallet identifier
1750
 * @param path                  string      the parent path for which to get a new derivation,
1751
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1752
 * @param [cb]                  function    callback(err, result)
1753
 * @returns {q.Promise}
1754
 */
1755
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1756
    var self = this;
1757
1758
    return self.client.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1759
};
1760
1761
1762
/**
1763
 * delete the wallet
1764
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1765
 *  is required to be able to delete a wallet
1766
 *
1767
 * @param identifier            string      the wallet identifier
1768
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1769
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1770
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1771
 * @param [cb]                  function    callback(err, result)
1772
 * @returns {q.Promise}
1773
 */
1774
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1775
    var self = this;
1776
1777
    if (typeof force === "function") {
1778
        cb = force;
1779
        force = false;
1780
    }
1781
1782
    return self.client.delete("/wallet/" + identifier, {force: force}, {
1783
        checksum: checksumAddress,
1784
        signature: checksumSignature
1785
    }, cb);
1786
};
1787
1788
/**
1789
 * use the API to get the best inputs to use based on the outputs
1790
 *
1791
 * the return array has the following format:
1792
 * [
1793
 *  "utxos" => [
1794
 *      [
1795
 *          "hash" => "<txHash>",
1796
 *          "idx" => "<index of the output of that <txHash>",
1797
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1798
 *          "value" => 32746327,
1799
 *          "address" => "1address",
1800
 *          "path" => "m/44'/1'/0'/0/13",
1801
 *          "redeem_script" => "<redeemScript-hex>",
1802
 *      ],
1803
 *  ],
1804
 *  "fee"   => 10000,
1805
 *  "change"=> 1010109201,
1806
 * ]
1807
 *
1808
 * @param identifier        string      the wallet identifier
1809
 * @param pay               array       {'address': (int)value}     coins to send
1810
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1811
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1812
 * @param feeStrategy       string      defaults to
1813
 * @param options
1814
 * @param [cb]              function    callback(err, utxos, fee, change)
1815
 * @returns {q.Promise}
1816
 */
1817
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1818
    var self = this;
1819
1820
    if (typeof feeStrategy === "function") {
1821
        cb = feeStrategy;
1822
        feeStrategy = null;
1823
        options = {};
1824
    } else if (typeof options === "function") {
1825
        cb = options;
1826
        options = {};
1827
    }
1828
1829
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1830
    options = options || {};
1831
1832
    var deferred = q.defer();
1833
    deferred.promise.spreadNodeify(cb);
1834
1835
    var params = {
1836
        lock: lockUTXO,
1837
        zeroconf: allowZeroConf ? 1 : 0,
1838
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1839
        fee_strategy: feeStrategy
1840
    };
1841
1842
    if (options.forcefee) {
1843
        params['forcefee'] = options.forcefee;
1844
    }
1845
1846
    deferred.resolve(
1847
        self.client.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1848
            function(result) {
1849
                return [result.utxos, result.fee, result.change, result];
1850
            },
1851
            function(err) {
1852
                if (err.message.match(/too low to pay the fee/)) {
1853
                    throw blocktrail.WalletFeeError(err);
1854
                }
1855
1856
                throw err;
1857
            }
1858
        )
1859
    );
1860
1861
    return deferred.promise;
1862
};
1863
1864
/**
1865
 * @param [cb]              function    callback(err, utxos, fee, change)
1866
 * @returns {q.Promise}
1867
 */
1868
APIClient.prototype.feePerKB = function(cb) {
1869
    var self = this;
1870
1871
    var deferred = q.defer();
1872
    deferred.promise.spreadNodeify(cb);
1873
1874
    deferred.resolve(self.client.get("/fee-per-kb"));
1875
1876
    return deferred.promise;
1877
};
1878
1879
/**
1880
 * send the transaction using the API
1881
 *
1882
 * @param identifier        string      the wallet identifier
1883
 * @param txHex             string      partially signed transaction as hex string
1884
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1885
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1886
 * @param [twoFactorToken]  string      2FA token
1887
 * @param [prioboost]       bool
1888
 * @param [cb]              function    callback(err, txHash)
1889
 * @returns {q.Promise}
1890
 */
1891
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1892
    var self = this;
1893
1894
    if (typeof twoFactorToken === "function") {
1895
        cb = twoFactorToken;
1896
        twoFactorToken = null;
1897
        prioboost = false;
1898
    } else if (typeof prioboost === "function") {
1899
        cb = prioboost;
1900
        prioboost = false;
1901
    }
1902
1903
    var data = {
1904
        paths: paths,
1905
        two_factor_token: twoFactorToken
1906
    };
1907
    if (typeof txHex === "string") {
1908
        data.raw_transaction = txHex;
1909
    } else if (typeof txHex === "object") {
1910
        Object.keys(txHex).map(function(key) {
1911
            data[key] = txHex[key];
1912
        });
1913
    }
1914
1915
    return self.client.post(
1916
        "/wallet/" + identifier + "/send",
1917
        {
1918
            check_fee: checkFee ? 1 : 0,
1919
            prioboost: prioboost ? 1 : 0
1920
        },
1921
        data,
1922
        cb
1923
    );
1924
};
1925
1926
/**
1927
 * setup a webhook for this wallet
1928
 *
1929
 * @param identifier        string      the wallet identifier
1930
 * @param webhookIdentifier string      identifier for the webhook
1931
 * @param url               string      URL to receive webhook events
1932
 * @param [cb]              function    callback(err, webhook)
1933
 * @returns {q.Promise}
1934
 */
1935
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
1936
    var self = this;
1937
1938
    return self.client.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
1939
};
1940
1941
/**
1942
 * delete a webhook that was created for this wallet
1943
 *
1944
 * @param identifier        string      the wallet identifier
1945
 * @param webhookIdentifier string      identifier for the webhook
1946
 * @param [cb]              function    callback(err, success)
1947
 * @returns {q.Promise}
1948
 */
1949
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
1950
    var self = this;
1951
1952
    return self.client.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
1953
};
1954
1955
/**
1956
 * get all transactions for an wallet (paginated)
1957
 *
1958
 * @param identifier    string      wallet identifier
1959
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1960
 * @param [cb]          function    callback function to call when request is complete
1961
 * @return q.Promise
1962
 */
1963
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
1964
    var self = this;
1965
1966
    if (typeof params === "function") {
1967
        cb = params;
1968
        params = null;
1969
    }
1970
1971
    return self.client.get("/wallet/" + identifier + "/transactions", params, true, cb);
1972
};
1973
1974
/**
1975
 * get all addresses for an wallet (paginated)
1976
 *
1977
 * @param identifier    string      wallet identifier
1978
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1979
 * @param [cb]          function    callback function to call when request is complete
1980
 * @return q.Promise
1981
 */
1982
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
1983
    var self = this;
1984
1985
    if (typeof params === "function") {
1986
        cb = params;
1987
        params = null;
1988
    }
1989
1990
    return self.client.get("/wallet/" + identifier + "/addresses", params, true, cb);
1991
};
1992
1993
/**
1994
 * @param identifier    string      wallet identifier
1995
 * @param address       string      the address to label
1996
 * @param label         string      the label
1997
 * @param [cb]          function    callback(err, res)
1998
 * @return q.Promise
1999
 */
2000
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2001
    var self = this;
2002
2003
    return self.client.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2004
};
2005
2006
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2007
    var self = this;
2008
2009
    if (typeof feeStrategy === "function") {
2010
        cb = feeStrategy;
2011
        feeStrategy = null;
2012
    } else if (typeof options === "function") {
2013
        cb = options;
2014
        options = {};
2015
    }
2016
2017
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2018
    options = options || {};
2019
2020
    var params = {
2021
        outputs: options.outputs ? options.outputs : 1,
2022
        zeroconf: allowZeroConf ? 1 : 0,
2023
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2024
        fee_strategy: feeStrategy
2025
    };
2026
2027
    if (options.forcefee) {
2028
        params['forcefee'] = options.forcefee;
2029
    }
2030
2031
    return self.client.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2032
};
2033
2034
/**
2035
 * get all UTXOs for an wallet (paginated)
2036
 *
2037
 * @param identifier    string      wallet identifier
2038
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2039
 * @param [cb]          function    callback function to call when request is complete
2040
 * @return q.Promise
2041
 */
2042
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2043
    var self = this;
2044
2045
    if (typeof params === "function") {
2046
        cb = params;
2047
        params = null;
2048
    }
2049
2050
    return self.client.get("/wallet/" + identifier + "/utxos", params, true, cb);
2051
};
2052
2053
/**
2054
 * get a paginated list of all wallets associated with the api user
2055
 *
2056
 * @param [params]      object      pagination: {page: 1, limit: 20}
2057
 * @param [cb]          function    callback function to call when request is complete
2058
 * @return q.Promise
2059
 */
2060
APIClient.prototype.allWallets = function(params, cb) {
2061
    var self = this;
2062
2063
    if (typeof params === "function") {
2064
        cb = params;
2065
        params = null;
2066
    }
2067
2068
    return self.client.get("/wallets", params, true, cb);
2069
};
2070
2071
/**
2072
 * verify a message signed bitcoin-core style
2073
 *
2074
 * @param message        string
2075
 * @param address        string
2076
 * @param signature      string
2077
 * @param [cb]          function    callback function to call when request is complete
2078
 * @return q.Promise
2079
 */
2080
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2081
    var self = this;
2082
2083
    // we could also use the API instead of the using bitcoinjs-lib to verify
2084
    // return self.client.post("/verify_message", null, {message: message, address: address, signature: signature}, cb);
2085
2086
    var deferred = q.defer();
2087
    deferred.promise.nodeify(cb);
2088
    try {
2089
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2090
        deferred.resolve(result);
2091
    } catch (e) {
2092
        deferred.reject(e);
2093
    }
2094
2095
    return deferred.promise;
2096
};
2097
2098
/**
2099
 * max is 0.001
2100
 * testnet only
2101
 *
2102
 * @param address
2103
 * @param amount
2104
 * @param cb
2105
 */
2106
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2107
    var self = this;
2108
2109
    return self.client.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2110
};
2111
2112
/**
2113
 * send a raw transaction
2114
 *
2115
 * @param rawTransaction    string      raw transaction as HEX
2116
 * @param [cb]              function    callback function to call when request is complete
2117
 * @return q.Promise
2118
 */
2119
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2120
    var self = this;
2121
2122
    return self.client.post("/send-raw-tx", null, rawTransaction, cb);
2123
};
2124
2125
/**
2126
 * get the current price index
2127
 *
2128
 * @param [cb]          function    callback({'USD': 287.30})
2129
 * @return q.Promise
2130
 */
2131
APIClient.prototype.price = function(cb) {
2132
    var self = this;
2133
2134
    return self.client.get("/price", null, false, cb);
2135
};
2136
2137
module.exports = APIClient;
2138